<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        /*
            Promise.all应该符合以下特征
                1. 输入为Iterator类型的参数，可以为Array，Map，Set，String，也可能是{}或者魔改的Symbol.iterator
                2. 若输入的可迭代数据里不是Promise，则需要原样输出
                3. 返回一个Promise实例，可以调用then和catch方法
                4. then方法接收到的数据为保持原顺序的数组
                5. catch方法接收到的数据为第一个reject返回值
                6. 对于空Iterator，resolve返回空数组
        */
        function promiseAll(args) {
            return new Promise((resolve, reject) => { //因为all方法总归要返回promise，所以它的所有逻辑我们都在new Promise的执行器里完成
                const result = []; //见上面的4. Promise.all方法如果resolve，那么then拿到的是一个数组，数组里面是所有Promise的resolve成功的值，所以我们的核心逻辑就是构造这个result数组
                let iteratorIndex = 0; //见上面的1.和4. 因为Promise.all方法接收的并不一定是数组，而是任何有Symbol.iterator属性的可迭代对象，所以我们下面遍历这个可迭代对象构造result时使用for of循环，for of拿到的是可迭代对象的迭代项的值，我们要保证result中项的顺序，所以我们要在for of循环外定义iteratorIndex用来当前处理的这个promise的resolve的值应该在result的哪一项
                let resolveSum = 0; //resolveSum用来记录成功的promise的数量，如果resolveSum等于遍历的数量，就说明Promise.all方法应该resolve

                for(const item of Array.from(args || [])) { //使用for of 遍历可迭代对象args
                    let resultIndex = iteratorIndex; //resultIndex属于for of的一次循环的块级作用域，在这个块级作用域中，下面then和catch都是异步代码，如果异步代码中访问resultIndex这种块级作用域中定义的变量：异步代码在执行时resultIndex某种程度上说已经消失了，所以可以理解为在块级作用域释放里面的局部变量时会把局部变量的值赋给块级作用域中的异步代码，保证异步代码的正确执行。或者还有一种理解：如果块级作用域中有异步代码，并且异步代码中访问了块级作用域中的局部变量，那么这个块级作用域整体会保存至异步代码完全执行完毕（这个理解比较靠谱）
                    iteratorIndex += 1; //更新iteratorIndex，为下一轮for of做准备

                    /*
                        for of循环体内的逻辑就是判断item是一个成功的promise或者失败的promise，如果是成功的就把成功的值放入result，在resolveSum === iteratorIndex时，即全部promise都成功时resolve，如果失败就直接reject(err)
                    */
                    Promise.resolve(item).then((res) => { //见2. 对于不是promise的item，我们先包装一层Promise.resolve()，这样也不会影响promise对象
                        result[resultIndex] = res; //如果这个item为成功的promise就在result数组中记录它成功的值即可
                        resolveSum += 1; //更新成功的个数
                        if(resolveSum === iteratorIndex) { //Promise.all resolve的判断条件
                            resolve(result);
                        }
                    }).catch(err => { //如果失败，Promise.all直接reject
                        reject(err);
                    })
                }
                if(iteratorIndex === 0) { //见上面的6. 如果传入的是空迭代对象，比如[]，不会进入for of循环，这里进行判断，直接resolve([])
                    resolve(result);
                }
            })
        }
        let p1 = new Promise((resolve) => {
            setTimeout(resolve.bind(null, 1),1000);
        });
        let p2 = new Promise((resolve) => {
            setTimeout(resolve.bind(null, 2),1000);
        })
        let p3 = new Promise((resolve) => {
            setTimeout(resolve.bind(null, 3),1000);
        })
        function* gen() {
            yield 1;
            yield 2;
            yield 3;
            return 4;
        }
        promiseAll([]).then(v => {
            console.log(v); // []
        })
        promiseAll([p1, p2, p3]).then(v => {
            console.log(v); // [1, 2, 3]
        })
        promiseAll("123").then(v => {
            console.log(v); // ["1", "2", "3"]
        })
        promiseAll(gen()).then(v => {
            console.log(v); // [1, 2, 3]  Generator函数实例（迭代器对象）遍历的就是yield的值
        })


        promiseAll({}).then(v => { //对于promiseAll接收空对象{}的情况，for of遍历会报错，我们使用Array.from包装args，这样包装不会影响可迭代对象，而且接收{}时会转化为空的可迭代对象，所以修改为：for(const item of Array.from(args)) {
            console.log(v); // []
        })

        promiseAll().then(v => { //对于promiseAll没有接收参数的情况，我们修改:for(const item of Array.from(args || []))
            console.log(v); // []
        })
        
        
    </script>

<script>
// 2023.7.20 手写简易版
function promiseAll(promiseArr) {
    const result = [];
    let hadSolved = 0;
    return new Promise((resolve, reject) => {
        for(let i = 0; i < promiseArr.length; i ++) {
            promiseArr[i].then((res) => {
                hadSolved ++;
                result[i] = res;
                if(hadSolved === promiseArr.length) {
                    resolve(result);
                }
            }).catch((err) => {
                reject(err);
            })
        }
    })
}
// let p1 = new Promise((resolve) => {
//     setTimeout(resolve.bind(null, 1),1000);
// });
// let p2 = new Promise((resolve) => {
//     setTimeout(resolve.bind(null, 2),1000);
// })
// let p3 = new Promise((resolve) => {
//     setTimeout(resolve.bind(null, 3),1000);
// })
// promiseAll([p1, p2, p3]).then((res) => {
//     console.log(res);
// });
</script>
</body>
</html>